use std::env;
use pest::Parser;
use pest_derive::Parser;

pub mod formatter;

#[derive(Parser)]
#[grammar = "parser.pest"] // 指向你的 .pest 文件
struct SysYParser;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        std::process::exit(1);
    }
    let filename = &args[1];
    let input = std::fs::read_to_string(filename).expect("Failed to read input");

    let mut errors = Vec::new();
    let pairs = SysYParser::parse(Rule::program, &input);
    let mut f = formatter::Formatter::new();
    match pairs {
        Ok(mut pairs) => {
            if let Some(p) = pairs.next() {
                f.format(p);
            }
        }
        Err(error) => {
            let mut cursor;
            match error.location {
                pest::error::InputLocation::Pos(p) => { cursor = p; }
                pest::error::InputLocation::Span((_start, end)) => { cursor = end; }
            }
            if let Some(pos) = input[cursor..].find('\n') { cursor += pos; }
            match error.line_col {
                pest::error::LineColLocation::Pos(p) => { errors.push((p.0, error)) }
                pest::error::LineColLocation::Span(p1, _p2) => { errors.push((p1.0, error)) }
            }
            for error in handle_error(&input, cursor) { errors.push(error); }
        }
    }
    if errors.is_empty() { f.print(); }
    else { 
        for (line, e) in errors {
            println!("Error type B at Line {}: {}", line, e.variant.message());
        }
    }
}

fn handle_error(input: &str, mut cursor: usize) -> Vec<(usize, pest::error::Error<Rule>)> {
    let mut errors = Vec::new();
    let mut current = input[cursor..].to_string();

    while !current.trim().is_empty() {
        match SysYParser::parse(Rule::Recoverable, &current) {
            Ok(mut pairs) => {
                let pair = pairs.next().unwrap();
                cursor += pair.as_str().len();
                current = current.replacen(pair.as_str(), "", 1);
            }
            Err(error) => {
                for c in current.chars() {
                    if c.is_whitespace() {
                        cursor += 1;
                    }
                    else { break; }
                }
                current = input[cursor..].to_string();
                let line = get_line_number(input, cursor);
                errors.push((line, error));
                if let Some(pos) = current.find(|c| is_synchronizing_token(c)) {
                    cursor += pos + 1;
                    current = current[pos+2..].to_string();
                }
                else { break; }
            }
        }
    }
    errors
}

fn is_synchronizing_token(c: char) -> bool {
    matches!(c, ';' | '}')
}
fn get_line_number(text: &str, char_index: usize) -> usize {
    let mut line_number = 1;
    for (index, c) in text.chars().enumerate() {
        if char_index < index { break; }
        if c == '\n' {
            line_number += 1;
        }
    }
    
    line_number
}
